Acknowledgements

Material created by Peter Mac Data Science.

Objectives

  • Demonstrate how R can be used to visualise RNA-Seq data with volcano plots
  • Demonstrate basic R syntax (ifelse(), c(), %in%)
  • Utilize dplyr’s mutate to add columns to tables.
  • Utilize ggplot2’s geom_point()
  • Utilize ggrepel’s ggplot_text_repel()

Introduction

In this tutorial, we will learn some R through creating volcano plots from an RNA-seq experiment.

RNA-seq dataset

We will again use the published RNA-seq data from the Nature Cell Biology paper by Fu et al. 2015. This study examined expression in basal and luminal cells from mice at different stages (virgin, pregnant and lactating).

In the previous tutorial we explored the counts for each sample. Today we will work with the differential expression results. We will use the results from comparing the luminal cells from the mammary gland of the pregnant and lactating mice.This is a file that contains the log fold changes and P values for the genes.

   

Packages

We have already seen some tidyverse packages: readr for reading in files, ggplot2 for plotting. Today we will use those packages again aswell as another really useful tidyverse package called dplyr that can be used to manipulate tables. We will also use a non-tidyverse R package called ggrepel.

Loading the data

First let’s open a new R script. From the top menu in RStudio: File > New File > R Script. Let’s save it as volcano_plots.R.

We will begin by loading in the packages that we need. These are already installed for you but if you need to install them on your own computer you can use install.packages().

library(tidyverse)
library(ggrepel)

Next we load in our RNA-seq data. The file we will use is tab-separated, so we will use the read_tsv() function from the tidyverse readr package to read it in. We will store the contents of our file in an object called de_results.

de_results <- read_tsv("/training/r-intro-tidyverse/data/limma-voom_luminalpregnant-luminallactate.tsv.gz")
Parsed with column specification:
cols(
  ENTREZID = col_double(),
  SYMBOL = col_character(),
  GENENAME = col_character(),
  logFC = col_double(),
  AveExpr = col_double(),
  t = col_double(),
  P.Value = col_double(),
  adj.P.Val = col_double(),
  B = col_double()
)

There should be 15,804 rows and 8 columns. We can see that in the Environment tab on the right.

We can type de_results to have a look at the data.

de_results

We can’t see all the columns here but we can look at all the data with View() or use the R function colnames() if we just want to see the column names.

View(de_results)
colnames(de_results)

Creating a volcano plot

To make a volcano plot we make a scatterplot using geom_point() and plot the log fold change (logFC) vs the negative log10 P value. Why do we use the negative log 10 P value and not just the P value? Let’s have a look at what happens if we plot the P value as is versus the logFC. We use the columns called logFC and P.Value.

ggplot(de_results, aes(logFC, P.Value)) +
    geom_point()

Genes with small P values are the most significant and in this plot they are all squashed at the bottom of the plot, not very easy to see. Let’s see what happens when we use log10.

ggplot(de_results, aes(logFC, log10(P.Value))) +
    geom_point()

Looks better but this still has the most significant genes at the bottom and the usual way is to plot the most significant at the top so we use the negative log10 (-log10). Let’s try that.

ggplot(de_results, aes(logFC, -log10(P.Value))) +
    geom_point()

Highlighting significant genes

Using a P value threshold

Let’s colour genes that are significant. We will call genes significantly differentially expressed if they have an adjusted P Value below 0.05. An adjusted P value means the P value has been adjusted for multiple testing, as we are testing many thousands of genes. This is what we filter on in RNA-Seq.

Remember we saw in the previous tutorial that we can use a column to colour features of our data with fill= or col=. In this case we are colouring points so we use col=.

We will add a column that says whether genes are significant or not. To add columns we use dplyr’s mutate().

Let’s have a look at mutate first to see how it works. We give mutate our data (de_results), then a name for the column we want to create.

Let’s make a column called signif (for significance). Then we put what we want to have in the column in brackets after the column name. For example, if we wanted to label every gene as significant we could write below. We’ll try running it and store it in an object called testing as we’re just seeing how it works.

testing <- mutate(de_results, signif=("Signif"))

We can use View() to have a look at dataset. There should be a new column called signif at the end with “Signif” in every row. mutate always adds the column to the end of the table.

View(testing)

Exercise

Add another column called labels to the testing object that contains the value “Labels” in every row.

But obviously not all genes are significant. We want to only call genes significant if they have a P value below 0.05. We can use an R function called ifelse() to say if our adj.P.Val is below 0.05 add “Signif” to the sig column, otherwise add “Not signif”.

We can take a look at the ifelse help to see how it works. The help tells us the Usage is ifelse(test, yes, no). Our “test” is whether our gene has an adj.P.Val < 0.05, if the answer is yes, we’ll add “Signif” into the column, if the answer is no, then we’ll add “Not signif”. We’ll save the output as de_results (this overwrites the original de_results object).

de_results <- mutate(de_results, signif=ifelse(adj.P.Val < 0.05, "Signif", "Not signif"))

Let’s take a look at de_results again now. We should see we have a new column at the end called “signif”.

View(de_results)

In View() we can sort on the signif column to check we see both Signif and Not signif entries.

Now that we have a column that flags whether the genes are significant or not we can use that to colour the significant genes by adding col=signif to our ggplot.

ggplot(de_results, aes(logFC, -log10(P.Value), col=signif)) +
    geom_point()

We might want to change the colours.

There are built-in colour palettes, that can be handy to use, where the sets of colours are predefined. scale_colour_brewer() is a popular one (there is also scale_fill_brewer()). Take a look at the help for scale_colour_brewer to see what pallettes are available.

There’s one called “Dark2”, let’s have a look at that.

ggplot(de_results, aes(logFC, -log10(P.Value), col=signif)) +
  geom_point() +
  scale_colour_brewer(palette = "Dark2")

There’s one called “Set1”, let’s have a look at that.

ggplot(de_results, aes(logFC, -log10(P.Value), col=signif)) +
  geom_point() +
  scale_colour_brewer(palette = "Set1")

Or we could choose to set the colours manually. We could decide to colour the non-significant genes grey and the significant genes red. We are using col= so to specify our own colours we add + scale_colour_manual(values=)) (as we will see, we can keep adding layers to our plot with +). If we were using fill= we would use + scale_fill_manual(values=))

To use two colours we add + scale_colour_manual(values=c("red", "grey")). Note that here we see the function c() for the first time. We use function extremely often in R when we have multiple items that we are combining. Here we have two colours we want to use, so we need to use c() to combine them to give to values=. Thus we need to add values=c("red", "grey").

ggplot(de_results, aes(logFC, -log10(P.Value), col=signif)) +
  geom_point() +
  scale_colour_manual(values=c("red", "grey"))

Hmm this is the wrong way around, our significant points are grey. We could change the order of the colours in values=.

ggplot(de_results, aes(logFC, -log10(P.Value), col=signif)) +
  geom_point() +
  scale_colour_manual(values=c("grey", "red"))

Or we could specify which value in our signif column we want to map to each colour.

ggplot(de_results, aes(logFC, -log10(P.Value), col=signif)) +
    geom_point() +
  scale_colour_manual(values=c("Signif"="red", "Not signif"="grey"))

Exercise

Colour the volcanoplot using two different colours of your choice. You can use one of the palettes or type colours() to see what colours are available. Choose two nice colours or two ugly ones.

Using P value and logFC thresholds

We could colour our significant genes that are downregulated and the genes that are upregulated using separate colours, by changing our signif column. We could have three values instead of two, we could have “Up”“,”Down" and “Not signif”

Let’s colour significant genes > logFC of 1, red and < logFC -1, blue. To do this we change our signif column by adding another ifelse(). We are asking: if genes are significantly up, add the value “Up” otherwise if the genes are significantly down, add the value “Down” otherwise add the value “Not signif”.

To ask if are genes have an adj.P.Value < 0.05 and also have a logFC > 1, we use the syntax adj.P.Val < 0.05 & logFC > 1, note the &. If they are < 0.05 and have a logFC < -1 we use adj.P.Val < 0.05 & logFC > 1. We write this criteria as below and save as de_results again.

de_results <- mutate(de_results, signif=ifelse((adj.P.Val < 0.05 & logFC > 1), "Up", ifelse((adj.P.Val < 0.05 & logFC < -1), "Down", "Not signif")))

Let’s take a look at the output.

View(de_results)

Now that we have our signif column with three values, “Up”, “Down”, “Not signif”, we can colour the volcano plot points, up - red, not signif - grey, and down - blue.

ggplot(de_results, aes(logFC, -log10(P.Value), col=signif)) +
  geom_point() +
  scale_colour_manual(values=c("Up"="red", "Not signif"="grey", "Down"="blue"))

Labelling genes

Labelling genes of interest

We can label one or more genes of interest in a volcano plot. This enables us to visualize where these genes are in terms of significance and in comparison to the other genes. In the original paper using this dataset, there is a heatmap of 31 genes in Figure 6b. These genes are a set of 30 cytokines/growth factor identified as differentially expressed, and the authors’ main gene of interest, Mcl1. These genes are provided in the volcano genes file and shown below. We will label these genes in the volcano plot. We’ll read in the genes and store it in an object called goi.

goi <- read_tsv("/training/r-intro-tidyverse/data/volcano_genes")
Parsed with column specification:
cols(
  GeneID = col_character()
)

Let’s take a look at what’s in goi

goi

It’s gene symbols stored in a column. We need to convert these symbols out of the column format. We can do that with dplyr’s pull(). We give pull() the goi object and the name of the column we want to convert.

goi_syms <- pull(goi, GeneID)

Take a look.

goi_syms
 [1] "Mcl1"   "Hbegf"  "Tgfb2"  "Cxcl16" "Csf1"   "Pdgfb"  "Edn1"   "Lif"    "Kitl"   "Bmp1"   "Pdgfa"  "Cmtm3"  "Cx3cl1" "Ctgf"   "Wnt5a"  "Ptn"   
[17] "Spp1"   "Bmp3"   "Cmtm8"  "Gmfg"   "Cxcl2"  "Cxcl3"  "Il15"   "Egf"    "Cmtm7"  "Il34"   "Pdgfd"  "Nov"    "Cmtm6"  "Ccl28"  "Cxcl1" 

We’ll add another columns for these labels, let’s call it mygenes. We do this similar to what we did for the top 10 genes.

de_results <- mutate(de_results, mygenes=ifelse(SYMBOL %in% goi_syms, SYMBOL, ""))

Let’s make the volcano plot and label this custom set of genes. We add + geom_text() and add the labels into that. Note we are using + again and adding another layer to our plot. This time we are adding a layer of text (labels).

ggplot(de_results, aes(logFC, -log10(P.Value), col=signif)) +
  geom_point() +
  scale_colour_manual(values=c("Up"="red", "Not signif"="grey", "Down"="blue")) +
  geom_text(aes(label=mygenes), show.legend = FALSE)

That doesn’t look great as the labels are overlapping but we can fix that. We can replace + geom_text() with + geom_text_repel() from the package ggrepel.

Note that running geom_text_repel() on the server can be very slow (take minutes), think it may be the same issue reported in Stack Overflow here, but it should run a lot quicker on your laptop/desktop.

ggplot(de_results, aes(logFC, -log10(P.Value), col=signif)) +
  geom_point() +
  scale_colour_manual(values=c("Up"="red", "Not signif"="grey", "Down"="blue")) +
  geom_text_repel(aes(label=mygenes))

If we want, we can colour just the points red and blue, and leave the labels black. We do this by adding the col=signif into the geom_point() aes() instead of in the ggplot() aes().

ggplot(de_results, aes(logFC, -log10(P.Value))) +
  geom_point(aes(col=signif)) +
  scale_colour_manual(values=c("Up"="red", "Not signif"="grey", "Down"="blue")) +
  geom_text_repel(aes(label=mygenes), show.legend = FALSE)

Labelling top significant genes

We could also label the top significant genes with the gene names so we can see what they are. We have gene symbols in our de_results so we could use those.

Let’s label the top 10 most significant genes. To do this we first identify the top 10 genes by P.Value. The file is already sorted by P value and remember head() shows us the top 6 lines in a file. If we look at the help for ?head we see that there is a default argument n = 6L, this is why it returns 6 lines. We can change this to 10L to get the top 10 lines. We will store this at an object called top10.

top10 <- head(de_results, n = 10L)

Thake a look.

top10

We need to extract the gene symbols from the top_10 info and have them not in a column. We can do that with pull()

top10_syms <- pull(top10, SYMBOL)

Take a look.

top10_syms
 [1] "Csn1s2b"  "Slc25a1"  "Slc34a2"  "Atp2b2"   "Acacb"    "Slc30a2"  "Elovl5"   "Egf"      "Ceacam10" "Pmvk"    

Next we’ll add another column to say whether to label the point or not using the command below. We’ll call the column labels. We are using ifelse() again to say if the value in the SYMBOL column is in the top 10 symbols, add the symbol, otherwise leave the column empty (add “”). Here we see %in% being used. This is another very commonly used R function. We use %in% when we want to ask if a value is in set of values.

de_results <- mutate(de_results, labels=ifelse(SYMBOL %in% top10_syms, SYMBOL, ""))

Take a look.

de_results

Next we add + geom_text() and add the labels into that. Note we are using + again and adding another layer to our plot. This time we are adding a layer of text (labels).

ggplot(de_results, aes(logFC, -log10(P.Value), col=signif)) +
  geom_point() +
  scale_colour_manual(values=c("Up"="red", "Not signif"="grey", "Down"="blue")) +
  geom_text(aes(label=labels))

Again we could use geom_text_repel() to stop the labels overlapping.

The legend now has a legend for the labels overlapping but we can remove that by adding show.legend=FALSE.

ggplot(de_results, aes(logFC, -log10(P.Value), col=signif)) +
  geom_point() +
  scale_colour_manual(values=c("Up"="red", "Not signif"="grey", "Down"="blue")) +
  geom_text(aes(label=labels), show.legend = FALSE)

We’ll continue now without the gene labels (because geom_text_repel() can be slow).

Ordering the legend

What if we want the categories in the legend in a different order, for example, Up, Not signif, Down. We can addbreaks= with the order we want into scale_color_manual(). Note that here we use c() again to pass multiple values to breaks.

ggplot(de_results, aes(logFC, -log10(P.Value), col=signif)) +
  geom_point() +
  scale_colour_manual(values=c("Up"="red", "Not signif"="grey", "Down"="blue"), breaks=c("Up", "Down", "Not signif"))

Modifying the plot

If we want, we can just colour the points red and blue, and leave the labels black. We do this by adding the col=signif into the geom_point() aes() instead of in the ggplot() aes().

ggplot(de_results, aes(logFC, -log10(P.Value))) +
  geom_point(aes(col=signif)) +
  scale_colour_manual(values=c("Up"="red", "Not signif"="grey", "Down"="blue"))

We can also make the plot look nicer, for example, adjusting the x axis limits, adding a title and changing the background.

First let’s store our inital plot in an object called p, to make it easier to see what we’re changing.

p <- ggplot(de_results, aes(logFC, -log10(P.Value))) +
  geom_point(aes(col=signif)) +
  scale_colour_manual(values=c("Up"="red", "Not signif"="grey", "Down"="blue"))
p

Axis limits

We can adjust the x axis so it’s the same limit on the right and left.

p <- p + scale_x_continuous(limits=c(-10, 10))
p

Title

We can add a title.

p <- p + labs(title="Luminal pregnant vs lactating")
p

Themes

We can remove the grey background and grid lines. To do this we modify the ggplot theme. Themes are the non-data parts of the plot.

There are also a lot of built-in themes. Let’s have a look at a couple of the more widely used themes. We won’t save these (we won’t use p <-) we’ll just print them to have a look.

p + theme_bw()

p + theme_minimal()

There are many themes available, you can see some in the R graph gallery.

We can also modify parts of the theme individually. We can remove the grey background and grid lines with the code below.

p <- p + theme(panel.background = element_blank(), 
               panel.grid.major = element_blank(), 
               panel.grid.minor = element_blank())
p

We can add axis lines.

p <- p + theme(axis.line = element_line(size=0.2, colour = "black"))
p

We can remove the legend completely.

p <- p + theme(legend.position = "none")
p

We can centre the title.

p <- p + theme(plot.title = element_text(hjust = 0.5))
p

Exercise

Make a volcano plot for the basal cells using the file “https://zenodo.org/record/2596382/files/limma-voom_basalpregnant-basallactate”. Colour genes with adj.P.value < 0.01 and a logFC of >2 (and < -2). You can choose to use any colours and modify it whatever way you like.

Key Points

  • We can make a volcano plot with geom_point() by plotting the log fold change (logFC) vs the negative log10 P value
  • We can add columns with dplyr’s mutate
  • To highlight or label genes we add a column to specify which genes to colour or label
  • We can use ifelse() to test if values meet specified conditions
  • We use c() to combine multiple values
  • We use %in% to check if a value is in a set of values
  • We can use ggrepel to repel overlapping labels
  • We use themes to modify the non-data parts of a ggplot
LS0tCnRpdGxlOiAiSW50cm9kdWN0aW9uIHRvIFIiCmF1dGhvcjogIk1hcmlhIERveWxlIGFuZCBMaXogQ2hyaXN0aWUiCmRhdGU6ICJgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkICVCICVZJylgIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHllcwogICAgdG9jX2Zsb2F0OiB5ZXMKICAgIHRvY19kZXB0aDogMwpzdWJ0aXRsZTogdmlzdWFsaXNpbmcgUk5BLXNlcSBkYXRhIHdpdGggdGhlIHRpZHl2ZXJzZSAodm9sY2FubyBwbG90KQotLS0KCiMjIyMgQWNrbm93bGVkZ2VtZW50cwpNYXRlcmlhbCBjcmVhdGVkIGJ5IFBldGVyIE1hYyBEYXRhIFNjaWVuY2UuCgojIyBPYmplY3RpdmVzCgoqIERlbW9uc3RyYXRlIGhvdyBSIGNhbiBiZSB1c2VkIHRvIHZpc3VhbGlzZSBSTkEtU2VxIGRhdGEgd2l0aCB2b2xjYW5vIHBsb3RzCiogRGVtb25zdHJhdGUgYmFzaWMgUiBzeW50YXggKGBpZmVsc2UoKWAsIGBjKClgLCBgJWluJWApCiogVXRpbGl6ZSBkcGx5cidzIGBtdXRhdGVgIHRvIGFkZCBjb2x1bW5zIHRvIHRhYmxlcy4KKiBVdGlsaXplIGdncGxvdDIncyBgZ2VvbV9wb2ludCgpYAoqIFV0aWxpemUgZ2dyZXBlbCdzIGBnZ3Bsb3RfdGV4dF9yZXBlbGAoKQoKIyMgSW50cm9kdWN0aW9uCgpJbiB0aGlzIHR1dG9yaWFsLCB3ZSB3aWxsIGxlYXJuIHNvbWUgUiB0aHJvdWdoIGNyZWF0aW5nIHZvbGNhbm8gcGxvdHMgZnJvbSBhbiBSTkEtc2VxIGV4cGVyaW1lbnQuIAoKIyMjIFJOQS1zZXEgZGF0YXNldAoKV2Ugd2lsbCBhZ2FpbiB1c2UgdGhlIHB1Ymxpc2hlZCBSTkEtc2VxIGRhdGEgZnJvbSB0aGUgTmF0dXJlIENlbGwgQmlvbG9neSBwYXBlciBieSBbRnUgZXQgYWwuIDIwMTVdKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcHVibWVkLzI1NzMwNDcyKS4gVGhpcyBzdHVkeSBleGFtaW5lZCBleHByZXNzaW9uIGluIGJhc2FsIGFuZCBsdW1pbmFsIGNlbGxzIGZyb20gbWljZSBhdCBkaWZmZXJlbnQgc3RhZ2VzICh2aXJnaW4sIHByZWduYW50IGFuZCBsYWN0YXRpbmcpLiAKCiFbXShpbWFnZXMvbW91c2VfZXhwLnBuZykKCkluIHRoZSBwcmV2aW91cyB0dXRvcmlhbCB3ZSBleHBsb3JlZCB0aGUgY291bnRzIGZvciBlYWNoIHNhbXBsZS4gVG9kYXkgd2Ugd2lsbCB3b3JrIHdpdGggdGhlIGRpZmZlcmVudGlhbCBleHByZXNzaW9uIHJlc3VsdHMuIFdlIHdpbGwgdXNlIHRoZSByZXN1bHRzIGZyb20gY29tcGFyaW5nIHRoZSBsdW1pbmFsIGNlbGxzIGZyb20gdGhlIG1hbW1hcnkgZ2xhbmQgb2YgdGhlIHByZWduYW50IGFuZCBsYWN0YXRpbmcgbWljZS5UaGlzIGlzIGEgZmlsZSB0aGF0IGNvbnRhaW5zIHRoZSBsb2cgZm9sZCBjaGFuZ2VzIGFuZCBQIHZhbHVlcyBmb3IgdGhlIGdlbmVzLgoKXCAgClwgIAoKIyMjIFBhY2thZ2VzCgpXZSBoYXZlIGFscmVhZHkgc2VlbiBzb21lICoqdGlkeXZlcnNlKiogcGFja2FnZXM6ICoqcmVhZHIqKiBmb3IgcmVhZGluZyBpbiBmaWxlcywgKipnZ3Bsb3QyKiogZm9yIHBsb3R0aW5nLiBUb2RheSB3ZSB3aWxsIHVzZSB0aG9zZSBwYWNrYWdlcyBhZ2FpbiBhc3dlbGwgYXMgYW5vdGhlciByZWFsbHkgdXNlZnVsIHRpZHl2ZXJzZSBwYWNrYWdlIGNhbGxlZCAqKmRwbHlyKiogdGhhdCBjYW4gYmUgdXNlZCB0byBtYW5pcHVsYXRlIHRhYmxlcy4gV2Ugd2lsbCBhbHNvIHVzZSBhIG5vbi10aWR5dmVyc2UgUiBwYWNrYWdlIGNhbGxlZCBbZ2dyZXBlbF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2dncmVwZWwvdmlnbmV0dGVzL2dncmVwZWwuaHRtbCkuCgojIyBMb2FkaW5nIHRoZSBkYXRhCgpGaXJzdCBsZXQncyBvcGVuIGEgbmV3IFIgc2NyaXB0LiBGcm9tIHRoZSB0b3AgbWVudSBpbiBSU3R1ZGlvOiBgRmlsZSA+IE5ldyBGaWxlID4gUiBTY3JpcHRgLgpMZXQncyBzYXZlIGl0IGFzIGB2b2xjYW5vX3Bsb3RzLlJgLgoKV2Ugd2lsbCBiZWdpbiBieSBsb2FkaW5nIGluIHRoZSBwYWNrYWdlcyB0aGF0IHdlIG5lZWQuIFRoZXNlIGFyZSBhbHJlYWR5IGluc3RhbGxlZCBmb3IgeW91IGJ1dCBpZiB5b3UgbmVlZCB0byBpbnN0YWxsIHRoZW0gb24geW91ciBvd24gY29tcHV0ZXIgeW91IGNhbiB1c2UgYGluc3RhbGwucGFja2FnZXMoKWAuCgpgYGB7ciwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoZ2dyZXBlbCkKYGBgCgpOZXh0IHdlIGxvYWQgaW4gb3VyIFJOQS1zZXEgZGF0YS4gVGhlIGZpbGUgd2Ugd2lsbCB1c2UgaXMgdGFiLXNlcGFyYXRlZCwgc28gd2Ugd2lsbCB1c2UgdGhlIGByZWFkX3RzdigpYCBmdW5jdGlvbiBmcm9tIHRoZSB0aWR5dmVyc2UgcmVhZHIgcGFja2FnZSB0byByZWFkIGl0IGluLiBXZSB3aWxsIHN0b3JlIHRoZSBjb250ZW50cyBvZiBvdXIgZmlsZSBpbiBhbiBvYmplY3QgY2FsbGVkIGBkZV9yZXN1bHRzYC4KCmBgYHtyfQpkZV9yZXN1bHRzIDwtIHJlYWRfdHN2KCIvdHJhaW5pbmcvci1pbnRyby10aWR5dmVyc2UvZGF0YS9saW1tYS12b29tX2x1bWluYWxwcmVnbmFudC1sdW1pbmFsbGFjdGF0ZS50c3YuZ3oiKQpgYGAKClRoZXJlIHNob3VsZCBiZSAxNSw4MDQgcm93cyBhbmQgOCBjb2x1bW5zLiBXZSBjYW4gc2VlIHRoYXQgaW4gdGhlIEVudmlyb25tZW50IHRhYiBvbiB0aGUgcmlnaHQuCgpXZSBjYW4gdHlwZSBgZGVfcmVzdWx0c2AgdG8gaGF2ZSBhIGxvb2sgYXQgdGhlIGRhdGEuCmBgYHtyfQpkZV9yZXN1bHRzCmBgYAoKV2UgY2FuJ3Qgc2VlIGFsbCB0aGUgY29sdW1ucyBoZXJlIGJ1dCB3ZSBjYW4gbG9vayBhdCBhbGwgdGhlIGRhdGEgd2l0aCBgVmlldygpYCBvciB1c2UgdGhlIFIgZnVuY3Rpb24gYGNvbG5hbWVzKClgIGlmIHdlIGp1c3Qgd2FudCB0byBzZWUgdGhlIGNvbHVtbiBuYW1lcy4KCmBgYHtyIGV2YWw9RkFMU0V9ClZpZXcoZGVfcmVzdWx0cykKYGBgCgpgYGB7ciBldmFsPUZBTFNFfQpjb2xuYW1lcyhkZV9yZXN1bHRzKQpgYGAKCiMjIENyZWF0aW5nIGEgdm9sY2FubyBwbG90CgpUbyBtYWtlIGEgdm9sY2FubyBwbG90IHdlIG1ha2UgYSBzY2F0dGVycGxvdCB1c2luZyBgZ2VvbV9wb2ludCgpYCBhbmQgcGxvdCB0aGUgbG9nIGZvbGQgY2hhbmdlIChsb2dGQykgdnMgdGhlIG5lZ2F0aXZlIGxvZzEwIFAgdmFsdWUuIFdoeSBkbyB3ZSB1c2UgdGhlIG5lZ2F0aXZlIGxvZyAxMCBQIHZhbHVlIGFuZCBub3QganVzdCB0aGUgUCB2YWx1ZT8gTGV0J3MgaGF2ZSBhIGxvb2sgYXQgd2hhdCBoYXBwZW5zIGlmIHdlIHBsb3QgdGhlIFAgdmFsdWUgYXMgaXMgdmVyc3VzIHRoZSBsb2dGQy4gV2UgdXNlIHRoZSBjb2x1bW5zIGNhbGxlZCBsb2dGQyBhbmQgUC5WYWx1ZS4gCgpgYGB7cn0KZ2dwbG90KGRlX3Jlc3VsdHMsIGFlcyhsb2dGQywgUC5WYWx1ZSkpICsKICAgIGdlb21fcG9pbnQoKQpgYGAKR2VuZXMgd2l0aCBzbWFsbCBQIHZhbHVlcyBhcmUgdGhlIG1vc3Qgc2lnbmlmaWNhbnQgYW5kIGluIHRoaXMgcGxvdCB0aGV5IGFyZSBhbGwgc3F1YXNoZWQgYXQgdGhlIGJvdHRvbSBvZiB0aGUgcGxvdCwgbm90IHZlcnkgZWFzeSB0byBzZWUuIExldCdzIHNlZSB3aGF0IGhhcHBlbnMgd2hlbiB3ZSB1c2UgbG9nMTAuCgpgYGB7cn0KZ2dwbG90KGRlX3Jlc3VsdHMsIGFlcyhsb2dGQywgbG9nMTAoUC5WYWx1ZSkpKSArCiAgICBnZW9tX3BvaW50KCkKYGBgCgpMb29rcyBiZXR0ZXIgYnV0IHRoaXMgc3RpbGwgaGFzIHRoZSBtb3N0IHNpZ25pZmljYW50IGdlbmVzIGF0IHRoZSBib3R0b20gYW5kIHRoZSB1c3VhbCB3YXkgaXMgdG8gcGxvdCB0aGUgbW9zdCBzaWduaWZpY2FudCBhdCB0aGUgdG9wIHNvIHdlIHVzZSB0aGUgbmVnYXRpdmUgbG9nMTAgKC1sb2cxMCkuIExldCdzIHRyeSB0aGF0LgoKYGBge3J9CmdncGxvdChkZV9yZXN1bHRzLCBhZXMobG9nRkMsIC1sb2cxMChQLlZhbHVlKSkpICsKICAgIGdlb21fcG9pbnQoKQpgYGAKCiMjIEhpZ2hsaWdodGluZyBzaWduaWZpY2FudCBnZW5lcwoKIyMjIFVzaW5nIGEgUCB2YWx1ZSB0aHJlc2hvbGQKCkxldCdzIGNvbG91ciBnZW5lcyB0aGF0IGFyZSBzaWduaWZpY2FudC4gV2Ugd2lsbCBjYWxsIGdlbmVzIHNpZ25pZmljYW50bHkgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkIGlmIHRoZXkgaGF2ZSBhbiBhZGp1c3RlZCBQIFZhbHVlIGJlbG93IDAuMDUuIEFuIGFkanVzdGVkIFAgdmFsdWUgbWVhbnMgdGhlIFAgdmFsdWUgaGFzIGJlZW4gYWRqdXN0ZWQgZm9yIG11bHRpcGxlIHRlc3RpbmcsIGFzIHdlIGFyZSB0ZXN0aW5nIG1hbnkgdGhvdXNhbmRzIG9mIGdlbmVzLiBUaGlzIGlzIHdoYXQgd2UgZmlsdGVyIG9uIGluIFJOQS1TZXEuCgpSZW1lbWJlciB3ZSBzYXcgaW4gdGhlIHByZXZpb3VzIHR1dG9yaWFsIHRoYXQgd2UgY2FuIHVzZSBhIGNvbHVtbiB0byBjb2xvdXIgZmVhdHVyZXMgb2Ygb3VyIGRhdGEgd2l0aCBgZmlsbD1gIG9yIGBjb2w9YC4gSW4gdGhpcyBjYXNlIHdlIGFyZSBjb2xvdXJpbmcgcG9pbnRzIHNvIHdlIHVzZSBgY29sPWAuIAoKV2Ugd2lsbCBhZGQgYSBjb2x1bW4gdGhhdCBzYXlzIHdoZXRoZXIgZ2VuZXMgYXJlIHNpZ25pZmljYW50IG9yIG5vdC4gVG8gYWRkIGNvbHVtbnMgd2UgdXNlIGRwbHlyJ3MgYG11dGF0ZSgpYC4KCkxldCdzIGhhdmUgYSBsb29rIGF0IGBtdXRhdGVgIGZpcnN0IHRvIHNlZSBob3cgaXQgd29ya3MuIFdlIGdpdmUgbXV0YXRlIG91ciBkYXRhIChgZGVfcmVzdWx0c2ApLCB0aGVuIGEgbmFtZSBmb3IgdGhlIGNvbHVtbiB3ZSB3YW50IHRvIGNyZWF0ZS4gCgpMZXQncyBtYWtlIGEgY29sdW1uIGNhbGxlZCBzaWduaWYgKGZvciBzaWduaWZpY2FuY2UpLiBUaGVuIHdlIHB1dCB3aGF0IHdlIHdhbnQgdG8gaGF2ZSBpbiB0aGUgY29sdW1uIGluIGJyYWNrZXRzIGFmdGVyIHRoZSBjb2x1bW4gbmFtZS4gRm9yIGV4YW1wbGUsIGlmIHdlIHdhbnRlZCB0byBsYWJlbCBldmVyeSBnZW5lIGFzIHNpZ25pZmljYW50IHdlIGNvdWxkIHdyaXRlIGJlbG93LiBXZSdsbCB0cnkgcnVubmluZyBpdCBhbmQgc3RvcmUgaXQgaW4gYW4gb2JqZWN0IGNhbGxlZCBgdGVzdGluZ2AgYXMgd2UncmUganVzdCBzZWVpbmcgaG93IGl0IHdvcmtzLgoKYGBge3J9CnRlc3RpbmcgPC0gbXV0YXRlKGRlX3Jlc3VsdHMsIHNpZ25pZj0oIlNpZ25pZiIpKQpgYGAKCldlIGNhbiB1c2UgVmlldygpIHRvIGhhdmUgYSBsb29rIGF0IGRhdGFzZXQuIFRoZXJlIHNob3VsZCBiZSBhIG5ldyBjb2x1bW4gY2FsbGVkIHNpZ25pZiBhdCB0aGUgZW5kIHdpdGggIlNpZ25pZiIgaW4gZXZlcnkgcm93LiBtdXRhdGUgYWx3YXlzIGFkZHMgdGhlIGNvbHVtbiB0byB0aGUgKmVuZCogb2YgdGhlIHRhYmxlLgoKYGBge3IgZXZhbD1GQUxTRX0KVmlldyh0ZXN0aW5nKQpgYGAKCiMjIyMgRXhlcmNpc2UKQWRkIGFub3RoZXIgY29sdW1uIGNhbGxlZCBsYWJlbHMgdG8gdGhlIGB0ZXN0aW5nYCBvYmplY3QgdGhhdCBjb250YWlucyB0aGUgdmFsdWUgIkxhYmVscyIgaW4gZXZlcnkgcm93LgoKQnV0IG9idmlvdXNseSBub3QgYWxsIGdlbmVzIGFyZSBzaWduaWZpY2FudC4gV2Ugd2FudCB0byBvbmx5IGNhbGwgZ2VuZXMgc2lnbmlmaWNhbnQgaWYgdGhleSBoYXZlIGEgUCB2YWx1ZSBiZWxvdyAwLjA1LiBXZSBjYW4gdXNlIGFuIFIgZnVuY3Rpb24gY2FsbGVkIGBpZmVsc2UoKWAgdG8gc2F5IGlmIG91ciBhZGouUC5WYWwgaXMgYmVsb3cgMC4wNSBhZGQgIlNpZ25pZiIgdG8gdGhlIHNpZyBjb2x1bW4sIG90aGVyd2lzZSBhZGQgIk5vdCBzaWduaWYiLgoKV2UgY2FuIHRha2UgYSBsb29rIGF0IHRoZSBpZmVsc2UgaGVscCB0byBzZWUgaG93IGl0IHdvcmtzLiBUaGUgaGVscCB0ZWxscyB1cyB0aGUgVXNhZ2UgaXMgYGlmZWxzZSh0ZXN0LCB5ZXMsIG5vKWAuIE91ciAidGVzdCIgaXMgd2hldGhlciBvdXIgZ2VuZSBoYXMgYW4gYWRqLlAuVmFsIDwgMC4wNSwgaWYgdGhlIGFuc3dlciBpcyB5ZXMsIHdlJ2xsIGFkZCAiU2lnbmlmIiBpbnRvIHRoZSBjb2x1bW4sIGlmIHRoZSBhbnN3ZXIgaXMgbm8sIHRoZW4gd2UnbGwgYWRkICJOb3Qgc2lnbmlmIi4gV2UnbGwgc2F2ZSB0aGUgb3V0cHV0IGFzIGBkZV9yZXN1bHRzYCAodGhpcyBvdmVyd3JpdGVzIHRoZSBvcmlnaW5hbCBgZGVfcmVzdWx0c2Agb2JqZWN0KS4KCmBgYHtyfQpkZV9yZXN1bHRzIDwtIG11dGF0ZShkZV9yZXN1bHRzLCBzaWduaWY9aWZlbHNlKGFkai5QLlZhbCA8IDAuMDUsICJTaWduaWYiLCAiTm90IHNpZ25pZiIpKQpgYGAKCkxldCdzIHRha2UgYSBsb29rIGF0IGBkZV9yZXN1bHRzYCBhZ2FpbiBub3cuIFdlIHNob3VsZCBzZWUgd2UgaGF2ZSBhIG5ldyBjb2x1bW4gYXQgdGhlIGVuZCBjYWxsZWQgInNpZ25pZiIuCgpgYGB7ciBldmFsPUZBTFNFfQpWaWV3KGRlX3Jlc3VsdHMpCmBgYAoKSW4gVmlldygpIHdlIGNhbiBzb3J0IG9uIHRoZSBzaWduaWYgY29sdW1uIHRvIGNoZWNrIHdlIHNlZSBib3RoIFNpZ25pZiBhbmQgTm90IHNpZ25pZiBlbnRyaWVzLgoKTm93IHRoYXQgd2UgaGF2ZSBhIGNvbHVtbiB0aGF0IGZsYWdzIHdoZXRoZXIgdGhlIGdlbmVzIGFyZSBzaWduaWZpY2FudCBvciBub3Qgd2UgY2FuIHVzZSB0aGF0IHRvIGNvbG91ciB0aGUgc2lnbmlmaWNhbnQgZ2VuZXMgYnkgYWRkaW5nIGBjb2w9c2lnbmlmYCB0byBvdXIgZ2dwbG90LgoKYGBge3J9CmdncGxvdChkZV9yZXN1bHRzLCBhZXMobG9nRkMsIC1sb2cxMChQLlZhbHVlKSwgY29sPXNpZ25pZikpICsKICAgIGdlb21fcG9pbnQoKQpgYGAKCldlIG1pZ2h0IHdhbnQgdG8gY2hhbmdlIHRoZSBjb2xvdXJzLiAKClRoZXJlIGFyZSBidWlsdC1pbiBjb2xvdXIgcGFsZXR0ZXMsIHRoYXQgY2FuIGJlIGhhbmR5IHRvIHVzZSwgd2hlcmUgdGhlIHNldHMgb2YgY29sb3VycyBhcmUgcHJlZGVmaW5lZC4gYHNjYWxlX2NvbG91cl9icmV3ZXIoKWAgaXMgYSBwb3B1bGFyIG9uZSAodGhlcmUgaXMgYWxzbyBgc2NhbGVfZmlsbF9icmV3ZXIoKWApLiBUYWtlIGEgbG9vayBhdCB0aGUgaGVscCBmb3Igc2NhbGVfY29sb3VyX2JyZXdlciB0byBzZWUgd2hhdCBwYWxsZXR0ZXMgYXJlIGF2YWlsYWJsZS4KClRoZXJlJ3Mgb25lIGNhbGxlZCAiRGFyazIiLCBsZXQncyBoYXZlIGEgbG9vayBhdCB0aGF0LgoKYGBge3J9CmdncGxvdChkZV9yZXN1bHRzLCBhZXMobG9nRkMsIC1sb2cxMChQLlZhbHVlKSwgY29sPXNpZ25pZikpICsKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX2NvbG91cl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpCmBgYAoKVGhlcmUncyBvbmUgY2FsbGVkICJTZXQxIiwgbGV0J3MgaGF2ZSBhIGxvb2sgYXQgdGhhdC4KCmBgYHtyfQpnZ3Bsb3QoZGVfcmVzdWx0cywgYWVzKGxvZ0ZDLCAtbG9nMTAoUC5WYWx1ZSksIGNvbD1zaWduaWYpKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV9jb2xvdXJfYnJld2VyKHBhbGV0dGUgPSAiU2V0MSIpCmBgYAoKT3Igd2UgY291bGQgY2hvb3NlIHRvIHNldCB0aGUgY29sb3VycyBtYW51YWxseS4gV2UgY291bGQgZGVjaWRlIHRvIGNvbG91ciB0aGUgbm9uLXNpZ25pZmljYW50IGdlbmVzIGdyZXkgYW5kIHRoZSBzaWduaWZpY2FudCBnZW5lcyByZWQuIFdlIGFyZSB1c2luZyBgY29sPWAgc28gdG8gc3BlY2lmeSBvdXIgb3duIGNvbG91cnMgd2UgYWRkIGArIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPSkpYCAoYXMgd2Ugd2lsbCBzZWUsIHdlIGNhbiBrZWVwIGFkZGluZyBsYXllcnMgdG8gb3VyIHBsb3Qgd2l0aCBgK2ApLiBJZiB3ZSB3ZXJlIHVzaW5nIGBmaWxsPWAgd2Ugd291bGQgdXNlIGArIHNjYWxlX2ZpbGxfbWFudWFsKHZhbHVlcz0pKWAKClRvIHVzZSB0d28gY29sb3VycyB3ZSBhZGQgYCsgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9YygicmVkIiwgImdyZXkiKSlgLiBOb3RlIHRoYXQgaGVyZSB3ZSBzZWUgdGhlIGZ1bmN0aW9uIGBjKClgIGZvciB0aGUgZmlyc3QgdGltZS4gV2UgdXNlIGZ1bmN0aW9uIGV4dHJlbWVseSBvZnRlbiBpbiBSIHdoZW4gd2UgaGF2ZSBtdWx0aXBsZSBpdGVtcyB0aGF0IHdlIGFyZSAqY29tYmluaW5nKi4gSGVyZSB3ZSBoYXZlIHR3byBjb2xvdXJzIHdlIHdhbnQgdG8gdXNlLCBzbyB3ZSBuZWVkIHRvIHVzZSBgYygpYCB0byBjb21iaW5lIHRoZW0gdG8gZ2l2ZSB0byBgdmFsdWVzPWAuIFRodXMgd2UgbmVlZCB0byBhZGQgYHZhbHVlcz1jKCJyZWQiLCAiZ3JleSIpYC4KCmBgYHtyfQpnZ3Bsb3QoZGVfcmVzdWx0cywgYWVzKGxvZ0ZDLCAtbG9nMTAoUC5WYWx1ZSksIGNvbD1zaWduaWYpKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jKCJyZWQiLCAiZ3JleSIpKQpgYGAKCkhtbSB0aGlzIGlzIHRoZSB3cm9uZyB3YXkgYXJvdW5kLCBvdXIgc2lnbmlmaWNhbnQgcG9pbnRzIGFyZSBncmV5LiBXZSBjb3VsZCBjaGFuZ2UgdGhlIG9yZGVyIG9mIHRoZSBjb2xvdXJzIGluIGB2YWx1ZXM9YC4gCgpgYGB7cn0KZ2dwbG90KGRlX3Jlc3VsdHMsIGFlcyhsb2dGQywgLWxvZzEwKFAuVmFsdWUpLCBjb2w9c2lnbmlmKSkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9YygiZ3JleSIsICJyZWQiKSkKYGBgCgoKT3Igd2UgY291bGQgc3BlY2lmeSB3aGljaCB2YWx1ZSBpbiBvdXIgc2lnbmlmIGNvbHVtbiB3ZSB3YW50IHRvIG1hcCB0byBlYWNoIGNvbG91ci4KCmBgYHtyfQpnZ3Bsb3QoZGVfcmVzdWx0cywgYWVzKGxvZ0ZDLCAtbG9nMTAoUC5WYWx1ZSksIGNvbD1zaWduaWYpKSArCiAgICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoIlNpZ25pZiI9InJlZCIsICJOb3Qgc2lnbmlmIj0iZ3JleSIpKQpgYGAKIyMjIyBFeGVyY2lzZQpDb2xvdXIgdGhlIHZvbGNhbm9wbG90IHVzaW5nIHR3byBkaWZmZXJlbnQgY29sb3VycyBvZiB5b3VyIGNob2ljZS4gWW91IGNhbiB1c2Ugb25lIG9mIHRoZSBwYWxldHRlcyBvciB0eXBlIGBjb2xvdXJzKClgIHRvIHNlZSB3aGF0IGNvbG91cnMgYXJlIGF2YWlsYWJsZS4gQ2hvb3NlIHR3byBuaWNlIGNvbG91cnMgb3IgdHdvIHVnbHkgb25lcy4KCiMjIyBVc2luZyBQIHZhbHVlIGFuZCBsb2dGQyB0aHJlc2hvbGRzCgpXZSBjb3VsZCBjb2xvdXIgb3VyIHNpZ25pZmljYW50IGdlbmVzIHRoYXQgYXJlIGRvd25yZWd1bGF0ZWQgYW5kIHRoZSBnZW5lcyB0aGF0IGFyZSB1cHJlZ3VsYXRlZCB1c2luZyBzZXBhcmF0ZSBjb2xvdXJzLCBieSBjaGFuZ2luZyBvdXIgc2lnbmlmIGNvbHVtbi4gV2UgY291bGQgaGF2ZSB0aHJlZSB2YWx1ZXMgaW5zdGVhZCBvZiB0d28sIHdlIGNvdWxkIGhhdmUgIlVwIiIsICJEb3duIiBhbmQgIk5vdCBzaWduaWYiCgpMZXQncyBjb2xvdXIgc2lnbmlmaWNhbnQgZ2VuZXMgPiBsb2dGQyBvZiAxLCByZWQgYW5kIDwgbG9nRkMgLTEsIGJsdWUuIFRvIGRvIHRoaXMgd2UgY2hhbmdlIG91ciBzaWduaWYgY29sdW1uIGJ5IGFkZGluZyBhbm90aGVyIGBpZmVsc2UoKWAuIFdlIGFyZSBhc2tpbmc6CmlmIGdlbmVzIGFyZSBzaWduaWZpY2FudGx5IHVwLCBhZGQgdGhlIHZhbHVlICJVcCIKb3RoZXJ3aXNlIGlmIHRoZSBnZW5lcyBhcmUgc2lnbmlmaWNhbnRseSBkb3duLCBhZGQgdGhlIHZhbHVlICJEb3duIgpvdGhlcndpc2UgYWRkIHRoZSB2YWx1ZSAiTm90IHNpZ25pZiIuCgpUbyBhc2sgaWYgYXJlIGdlbmVzIGhhdmUgYW4gYWRqLlAuVmFsdWUgPCAwLjA1IGFuZCBhbHNvIGhhdmUgYSBsb2dGQyA+IDEsIHdlIHVzZSB0aGUgc3ludGF4IGBhZGouUC5WYWwgPCAwLjA1ICYgbG9nRkMgPiAxYCwgbm90ZSB0aGUgYCZgLiBJZiB0aGV5IGFyZSA8IDAuMDUgYW5kIGhhdmUgYSBsb2dGQyA8IC0xIHdlIHVzZSBgYWRqLlAuVmFsIDwgMC4wNSAmIGxvZ0ZDID4gMWAuIFdlIHdyaXRlIHRoaXMgY3JpdGVyaWEgYXMgYmVsb3cgYW5kIHNhdmUgYXMgYGRlX3Jlc3VsdHNgIGFnYWluLgoKYGBge3J9CmRlX3Jlc3VsdHMgPC0gbXV0YXRlKGRlX3Jlc3VsdHMsIHNpZ25pZj1pZmVsc2UoKGFkai5QLlZhbCA8IDAuMDUgJiBsb2dGQyA+IDEpLCAiVXAiLCBpZmVsc2UoKGFkai5QLlZhbCA8IDAuMDUgJiBsb2dGQyA8IC0xKSwgIkRvd24iLCAiTm90IHNpZ25pZiIpKSkKYGBgCgpMZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgb3V0cHV0LgoKYGBge3IgZXZhbD1GQUxTRX0KVmlldyhkZV9yZXN1bHRzKQpgYGAKCk5vdyB0aGF0IHdlIGhhdmUgb3VyIHNpZ25pZiBjb2x1bW4gd2l0aCB0aHJlZSB2YWx1ZXMsICJVcCIsICJEb3duIiwgIk5vdCBzaWduaWYiLCB3ZSBjYW4gY29sb3VyIHRoZSB2b2xjYW5vIHBsb3QgcG9pbnRzLCB1cCAtIHJlZCwgbm90IHNpZ25pZiAtIGdyZXksIGFuZCBkb3duIC0gYmx1ZS4KCmBgYHtyfQpnZ3Bsb3QoZGVfcmVzdWx0cywgYWVzKGxvZ0ZDLCAtbG9nMTAoUC5WYWx1ZSksIGNvbD1zaWduaWYpKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jKCJVcCI9InJlZCIsICJOb3Qgc2lnbmlmIj0iZ3JleSIsICJEb3duIj0iYmx1ZSIpKQpgYGAKCiMjIExhYmVsbGluZyBnZW5lcwoKCiMjIyBMYWJlbGxpbmcgZ2VuZXMgb2YgaW50ZXJlc3QKCldlIGNhbiBsYWJlbCBvbmUgb3IgbW9yZSBnZW5lcyBvZiBpbnRlcmVzdCBpbiBhIHZvbGNhbm8gcGxvdC4gVGhpcyBlbmFibGVzIHVzIHRvIHZpc3VhbGl6ZSB3aGVyZSB0aGVzZSBnZW5lcyBhcmUgaW4gdGVybXMgb2Ygc2lnbmlmaWNhbmNlIGFuZCBpbiBjb21wYXJpc29uIHRvIHRoZSBvdGhlciBnZW5lcy4gSW4gdGhlIG9yaWdpbmFsIHBhcGVyIHVzaW5nIHRoaXMgZGF0YXNldCwgdGhlcmUgaXMgYSBoZWF0bWFwIG9mIDMxIGdlbmVzIGluIEZpZ3VyZSA2Yi4gVGhlc2UgZ2VuZXMgYXJlIGEgc2V0IG9mIDMwIGN5dG9raW5lcy9ncm93dGggZmFjdG9yIGlkZW50aWZpZWQgYXMgZGlmZmVyZW50aWFsbHkgZXhwcmVzc2VkLCBhbmQgdGhlIGF1dGhvcnPigJkgbWFpbiBnZW5lIG9mIGludGVyZXN0LCBNY2wxLiBUaGVzZSBnZW5lcyBhcmUgcHJvdmlkZWQgaW4gdGhlIHZvbGNhbm8gZ2VuZXMgZmlsZSBhbmQgc2hvd24gYmVsb3cuIFdlIHdpbGwgbGFiZWwgdGhlc2UgZ2VuZXMgaW4gdGhlIHZvbGNhbm8gcGxvdC4gV2UnbGwgcmVhZCBpbiB0aGUgZ2VuZXMgYW5kIHN0b3JlIGl0IGluIGFuIG9iamVjdCBjYWxsZWQgYGdvaWAuCgpgYGB7cn0KZ29pIDwtIHJlYWRfdHN2KCIvdHJhaW5pbmcvci1pbnRyby10aWR5dmVyc2UvZGF0YS92b2xjYW5vX2dlbmVzIikKYGBgCgpMZXQncyB0YWtlIGEgbG9vayBhdCB3aGF0J3MgaW4gYGdvaWAKCmBgYHtyfQpnb2kKYGBgCgpJdCdzIGdlbmUgc3ltYm9scyBzdG9yZWQgaW4gYSBjb2x1bW4uIFdlIG5lZWQgdG8gY29udmVydCB0aGVzZSBzeW1ib2xzIG91dCBvZiB0aGUgY29sdW1uIGZvcm1hdC4gV2UgY2FuIGRvIHRoYXQgd2l0aCBkcGx5cidzIGBwdWxsKClgLiBXZSBnaXZlIGBwdWxsKClgIHRoZSBgZ29pYCBvYmplY3QgYW5kIHRoZSBuYW1lIG9mIHRoZSBjb2x1bW4gd2Ugd2FudCB0byBjb252ZXJ0LgoKYGBge3J9CmdvaV9zeW1zIDwtIHB1bGwoZ29pLCBHZW5lSUQpCmBgYAoKVGFrZSBhIGxvb2suCgpgYGB7cn0KZ29pX3N5bXMKYGBgCgpXZSdsbCBhZGQgYW5vdGhlciBjb2x1bW5zIGZvciB0aGVzZSBsYWJlbHMsIGxldCdzIGNhbGwgaXQgbXlnZW5lcy4gV2UgZG8gdGhpcyBzaW1pbGFyIHRvIHdoYXQgd2UgZGlkIGZvciB0aGUgdG9wIDEwIGdlbmVzLgoKYGBge3J9CmRlX3Jlc3VsdHMgPC0gbXV0YXRlKGRlX3Jlc3VsdHMsIG15Z2VuZXM9aWZlbHNlKFNZTUJPTCAlaW4lIGdvaV9zeW1zLCBTWU1CT0wsICIiKSkKYGBgCgpMZXQncyBtYWtlIHRoZSB2b2xjYW5vIHBsb3QgYW5kIGxhYmVsIHRoaXMgY3VzdG9tIHNldCBvZiBnZW5lcy4gV2UgYWRkIGArIGdlb21fdGV4dCgpYCBhbmQgYWRkIHRoZSBsYWJlbHMgaW50byB0aGF0LiBOb3RlIHdlIGFyZSB1c2luZyBgK2AgYWdhaW4gYW5kIGFkZGluZyBhbm90aGVyIGxheWVyIHRvIG91ciBwbG90LiBUaGlzIHRpbWUgd2UgYXJlIGFkZGluZyBhIGxheWVyIG9mIHRleHQgKGxhYmVscykuCgpgYGB7cn0KZ2dwbG90KGRlX3Jlc3VsdHMsIGFlcyhsb2dGQywgLWxvZzEwKFAuVmFsdWUpLCBjb2w9c2lnbmlmKSkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9YygiVXAiPSJyZWQiLCAiTm90IHNpZ25pZiI9ImdyZXkiLCAiRG93biI9ImJsdWUiKSkgKwogIGdlb21fdGV4dChhZXMobGFiZWw9bXlnZW5lcyksIHNob3cubGVnZW5kID0gRkFMU0UpCmBgYAoKVGhhdCBkb2Vzbid0IGxvb2sgZ3JlYXQgYXMgdGhlIGxhYmVscyBhcmUgb3ZlcmxhcHBpbmcgYnV0IHdlIGNhbiBmaXggdGhhdC4gV2UgY2FuIHJlcGxhY2UgYCsgZ2VvbV90ZXh0KClgIHdpdGggYCsgZ2VvbV90ZXh0X3JlcGVsKClgIGZyb20gdGhlIHBhY2thZ2UgKipnZ3JlcGVsKiouIAoKKk5vdGUgdGhhdCBydW5uaW5nIGBnZW9tX3RleHRfcmVwZWwoKWAgb24gdGhlIHNlcnZlciBjYW4gYmUgdmVyeSBzbG93ICh0YWtlIG1pbnV0ZXMpLCB0aGluayBpdCBtYXkgYmUgdGhlIHNhbWUgaXNzdWUgcmVwb3J0ZWQgaW4gU3RhY2sgT3ZlcmZsb3cgW2hlcmVdKGh0dHBzOi8vc3RhY2tvdmVyZmxvdy5jb20vcXVlc3Rpb25zLzU1OTQyNDk4L3Bsb3Qtd2l0aC1nZ3JlcGVsLWxhYmVscy1yZW5kZXJzLXZlcnktc2xvd2x5KSwgYnV0IGl0IHNob3VsZCBydW4gYSBsb3QgcXVpY2tlciBvbiB5b3VyIGxhcHRvcC9kZXNrdG9wLioKCmBgYHtyfQpnZ3Bsb3QoZGVfcmVzdWx0cywgYWVzKGxvZ0ZDLCAtbG9nMTAoUC5WYWx1ZSksIGNvbD1zaWduaWYpKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jKCJVcCI9InJlZCIsICJOb3Qgc2lnbmlmIj0iZ3JleSIsICJEb3duIj0iYmx1ZSIpKSArCiAgZ2VvbV90ZXh0X3JlcGVsKGFlcyhsYWJlbD1teWdlbmVzKSkKYGBgCgpJZiB3ZSB3YW50LCB3ZSBjYW4gY29sb3VyIGp1c3QgdGhlIHBvaW50cyByZWQgYW5kIGJsdWUsIGFuZCBsZWF2ZSB0aGUgbGFiZWxzIGJsYWNrLiBXZSBkbyB0aGlzIGJ5IGFkZGluZyB0aGUgYGNvbD1zaWduaWZgIGludG8gdGhlIGBnZW9tX3BvaW50KClgIGBhZXMoKWAgaW5zdGVhZCBvZiBpbiB0aGUgYGdncGxvdCgpYCBgYWVzKClgLgoKYGBge3J9CmdncGxvdChkZV9yZXN1bHRzLCBhZXMobG9nRkMsIC1sb2cxMChQLlZhbHVlKSkpICsKICBnZW9tX3BvaW50KGFlcyhjb2w9c2lnbmlmKSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoIlVwIj0icmVkIiwgIk5vdCBzaWduaWYiPSJncmV5IiwgIkRvd24iPSJibHVlIikpICsKICBnZW9tX3RleHRfcmVwZWwoYWVzKGxhYmVsPW15Z2VuZXMpLCBzaG93LmxlZ2VuZCA9IEZBTFNFKQpgYGAKCiMjIyBMYWJlbGxpbmcgdG9wIHNpZ25pZmljYW50IGdlbmVzCgpXZSBjb3VsZCBhbHNvIGxhYmVsIHRoZSB0b3Agc2lnbmlmaWNhbnQgZ2VuZXMgd2l0aCB0aGUgZ2VuZSBuYW1lcyBzbyB3ZSBjYW4gc2VlIHdoYXQgdGhleSBhcmUuIFdlIGhhdmUgZ2VuZSBzeW1ib2xzIGluIG91ciBgZGVfcmVzdWx0c2Agc28gd2UgY291bGQgdXNlIHRob3NlLiAKCkxldCdzIGxhYmVsIHRoZSB0b3AgMTAgbW9zdCBzaWduaWZpY2FudCBnZW5lcy4gVG8gZG8gdGhpcyB3ZSBmaXJzdCBpZGVudGlmeSB0aGUgdG9wIDEwIGdlbmVzIGJ5IFAuVmFsdWUuIFRoZSBmaWxlIGlzIGFscmVhZHkgc29ydGVkIGJ5IFAgdmFsdWUgYW5kIHJlbWVtYmVyIGhlYWQoKSBzaG93cyB1cyB0aGUgdG9wIDYgbGluZXMgaW4gYSBmaWxlLiBJZiB3ZSBsb29rIGF0IHRoZSBoZWxwIGZvciBgP2hlYWRgIHdlIHNlZSB0aGF0IHRoZXJlIGlzIGEgZGVmYXVsdCBhcmd1bWVudCBgbiA9IDZMYCwgdGhpcyBpcyB3aHkgaXQgcmV0dXJucyA2IGxpbmVzLiBXZSBjYW4gY2hhbmdlIHRoaXMgdG8gMTBMIHRvIGdldCB0aGUgdG9wIDEwIGxpbmVzLiBXZSB3aWxsIHN0b3JlIHRoaXMgYXQgYW4gb2JqZWN0IGNhbGxlZCBgdG9wMTBgLgoKYGBge3J9CnRvcDEwIDwtIGhlYWQoZGVfcmVzdWx0cywgbiA9IDEwTCkKYGBgCgpUaGFrZSBhIGxvb2suCgpgYGB7cn0KdG9wMTAKYGBgCgpXZSBuZWVkIHRvIGV4dHJhY3QgdGhlIGdlbmUgc3ltYm9scyBmcm9tIHRoZSBgdG9wXzEwYCBpbmZvIGFuZCBoYXZlIHRoZW0gbm90IGluIGEgY29sdW1uLiBXZSBjYW4gZG8gdGhhdCB3aXRoIGBwdWxsKClgCgpgYGB7cn0KdG9wMTBfc3ltcyA8LSBwdWxsKHRvcDEwLCBTWU1CT0wpCmBgYAoKVGFrZSBhIGxvb2suCgpgYGB7cn0KdG9wMTBfc3ltcwpgYGAKCk5leHQgd2UnbGwgYWRkIGFub3RoZXIgY29sdW1uIHRvIHNheSB3aGV0aGVyIHRvICpsYWJlbCogdGhlIHBvaW50IG9yIG5vdCB1c2luZyB0aGUgY29tbWFuZCBiZWxvdy4gV2UnbGwgY2FsbCB0aGUgY29sdW1uIGxhYmVscy4gV2UgYXJlIHVzaW5nIGBpZmVsc2UoKWAgYWdhaW4gdG8gc2F5IGlmIHRoZSB2YWx1ZSBpbiB0aGUgU1lNQk9MIGNvbHVtbiBpcyBpbiB0aGUgdG9wIDEwIHN5bWJvbHMsIGFkZCB0aGUgc3ltYm9sLCBvdGhlcndpc2UgbGVhdmUgdGhlIGNvbHVtbiBlbXB0eSAoYWRkICIiKS4gSGVyZSB3ZSBzZWUgYCVpbiVgIGJlaW5nIHVzZWQuIFRoaXMgaXMgYW5vdGhlciB2ZXJ5IGNvbW1vbmx5IHVzZWQgUiBmdW5jdGlvbi4gV2UgdXNlIGAlaW4lYCB3aGVuIHdlIHdhbnQgdG8gYXNrIGlmIGEgdmFsdWUgaXMgaW4gc2V0IG9mIHZhbHVlcy4gCgpgYGB7cn0KZGVfcmVzdWx0cyA8LSBtdXRhdGUoZGVfcmVzdWx0cywgbGFiZWxzPWlmZWxzZShTWU1CT0wgJWluJSB0b3AxMF9zeW1zLCBTWU1CT0wsICIiKSkKYGBgCgpUYWtlIGEgbG9vay4KCmBgYHtyfQpkZV9yZXN1bHRzCmBgYAoKTmV4dCB3ZSBhZGQgYCsgZ2VvbV90ZXh0KClgIGFuZCBhZGQgdGhlIGxhYmVscyBpbnRvIHRoYXQuIE5vdGUgd2UgYXJlIHVzaW5nIGArYCBhZ2FpbiBhbmQgYWRkaW5nIGFub3RoZXIgbGF5ZXIgdG8gb3VyIHBsb3QuIFRoaXMgdGltZSB3ZSBhcmUgYWRkaW5nIGEgbGF5ZXIgb2YgdGV4dCAobGFiZWxzKS4KCmBgYHtyfQpnZ3Bsb3QoZGVfcmVzdWx0cywgYWVzKGxvZ0ZDLCAtbG9nMTAoUC5WYWx1ZSksIGNvbD1zaWduaWYpKSArCiAgZ2VvbV9wb2ludCgpICsKICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jKCJVcCI9InJlZCIsICJOb3Qgc2lnbmlmIj0iZ3JleSIsICJEb3duIj0iYmx1ZSIpKSArCiAgZ2VvbV90ZXh0KGFlcyhsYWJlbD1sYWJlbHMpKQpgYGAKCkFnYWluIHdlIGNvdWxkIHVzZSBgZ2VvbV90ZXh0X3JlcGVsKClgIHRvIHN0b3AgdGhlIGxhYmVscyBvdmVybGFwcGluZy4KClRoZSBsZWdlbmQgbm93IGhhcyBhIGxlZ2VuZCBmb3IgdGhlIGxhYmVscyBvdmVybGFwcGluZyBidXQgd2UgY2FuIHJlbW92ZSB0aGF0IGJ5IGFkZGluZyBgc2hvdy5sZWdlbmQ9RkFMU0VgLgoKYGBge3J9CmdncGxvdChkZV9yZXN1bHRzLCBhZXMobG9nRkMsIC1sb2cxMChQLlZhbHVlKSwgY29sPXNpZ25pZikpICsKICBnZW9tX3BvaW50KCkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoIlVwIj0icmVkIiwgIk5vdCBzaWduaWYiPSJncmV5IiwgIkRvd24iPSJibHVlIikpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsPWxhYmVscyksIHNob3cubGVnZW5kID0gRkFMU0UpCmBgYAoKV2UnbGwgY29udGludWUgbm93IHdpdGhvdXQgdGhlIGdlbmUgbGFiZWxzIChiZWNhdXNlIGBnZW9tX3RleHRfcmVwZWwoKWAgY2FuIGJlIHNsb3cpLgoKIyMgT3JkZXJpbmcgdGhlIGxlZ2VuZAoKV2hhdCBpZiB3ZSB3YW50IHRoZSBjYXRlZ29yaWVzIGluIHRoZSBsZWdlbmQgaW4gYSBkaWZmZXJlbnQgb3JkZXIsIGZvciBleGFtcGxlLCBVcCwgTm90IHNpZ25pZiwgRG93bi4gV2UgY2FuIGFkZGAgYnJlYWtzPWAgd2l0aCB0aGUgb3JkZXIgd2Ugd2FudCBpbnRvIGBzY2FsZV9jb2xvcl9tYW51YWwoKWAuIE5vdGUgdGhhdCBoZXJlIHdlIHVzZSBgYygpYCBhZ2FpbiB0byBwYXNzIG11bHRpcGxlIHZhbHVlcyB0byBicmVha3MuCgpgYGB7cn0KZ2dwbG90KGRlX3Jlc3VsdHMsIGFlcyhsb2dGQywgLWxvZzEwKFAuVmFsdWUpLCBjb2w9c2lnbmlmKSkgKwogIGdlb21fcG9pbnQoKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9YygiVXAiPSJyZWQiLCAiTm90IHNpZ25pZiI9ImdyZXkiLCAiRG93biI9ImJsdWUiKSwgYnJlYWtzPWMoIlVwIiwgIkRvd24iLCAiTm90IHNpZ25pZiIpKQpgYGAKCgojIyBNb2RpZnlpbmcgdGhlIHBsb3QKCklmIHdlIHdhbnQsIHdlIGNhbiBqdXN0IGNvbG91ciB0aGUgcG9pbnRzIHJlZCBhbmQgYmx1ZSwgYW5kIGxlYXZlIHRoZSBsYWJlbHMgYmxhY2suIFdlIGRvIHRoaXMgYnkgYWRkaW5nIHRoZSBgY29sPXNpZ25pZmAgaW50byB0aGUgYGdlb21fcG9pbnQoKWAgYGFlcygpYCBpbnN0ZWFkIG9mIGluIHRoZSBgZ2dwbG90KClgIGBhZXMoKWAuCgpgYGB7cn0KZ2dwbG90KGRlX3Jlc3VsdHMsIGFlcyhsb2dGQywgLWxvZzEwKFAuVmFsdWUpKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbD1zaWduaWYpKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9YygiVXAiPSJyZWQiLCAiTm90IHNpZ25pZiI9ImdyZXkiLCAiRG93biI9ImJsdWUiKSkKYGBgCgpXZSBjYW4gYWxzbyBtYWtlIHRoZSBwbG90IGxvb2sgbmljZXIsIGZvciBleGFtcGxlLCBhZGp1c3RpbmcgdGhlIHggYXhpcyBsaW1pdHMsIGFkZGluZyBhIHRpdGxlIGFuZCBjaGFuZ2luZyB0aGUgYmFja2dyb3VuZC4gCgpGaXJzdCBsZXQncyBzdG9yZSBvdXIgaW5pdGFsIHBsb3QgaW4gYW4gb2JqZWN0IGNhbGxlZCBgcGAsIHRvIG1ha2UgaXQgZWFzaWVyIHRvIHNlZSB3aGF0IHdlJ3JlIGNoYW5naW5nLgoKYGBge3J9CnAgPC0gZ2dwbG90KGRlX3Jlc3VsdHMsIGFlcyhsb2dGQywgLWxvZzEwKFAuVmFsdWUpKSkgKwogIGdlb21fcG9pbnQoYWVzKGNvbD1zaWduaWYpKSArCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9YygiVXAiPSJyZWQiLCAiTm90IHNpZ25pZiI9ImdyZXkiLCAiRG93biI9ImJsdWUiKSkKcApgYGAKCiMjIyBBeGlzIGxpbWl0cwoKV2UgY2FuIGFkanVzdCB0aGUgeCBheGlzIHNvIGl0J3MgdGhlIHNhbWUgbGltaXQgb24gdGhlIHJpZ2h0IGFuZCBsZWZ0LgoKYGBge3J9CnAgPC0gcCArIHNjYWxlX3hfY29udGludW91cyhsaW1pdHM9YygtMTAsIDEwKSkKcApgYGAKCiMjIyBUaXRsZQoKV2UgY2FuIGFkZCBhIHRpdGxlLgoKYGBge3J9CnAgPC0gcCArIGxhYnModGl0bGU9Ikx1bWluYWwgcHJlZ25hbnQgdnMgbGFjdGF0aW5nIikKcApgYGAKCgojIyMgVGhlbWVzCgpXZSBjYW4gcmVtb3ZlIHRoZSBncmV5IGJhY2tncm91bmQgYW5kIGdyaWQgbGluZXMuIFRvIGRvIHRoaXMgd2UgbW9kaWZ5IHRoZSBnZ3Bsb3QgdGhlbWUuIFRoZW1lcyBhcmUgdGhlIG5vbi1kYXRhIHBhcnRzIG9mIHRoZSBwbG90LiAKClRoZXJlIGFyZSBhbHNvIGEgbG90IG9mIGJ1aWx0LWluIHRoZW1lcy4gTGV0J3MgaGF2ZSBhIGxvb2sgYXQgYSBjb3VwbGUgb2YgdGhlIG1vcmUgd2lkZWx5IHVzZWQgdGhlbWVzLiBXZSB3b24ndCBzYXZlIHRoZXNlICh3ZSB3b24ndCB1c2UgcCA8LSkgd2UnbGwganVzdCBwcmludCB0aGVtIHRvIGhhdmUgYSBsb29rLgoKYGBge3J9CnAgKyB0aGVtZV9idygpCmBgYApgYGB7cn0KcCArIHRoZW1lX21pbmltYWwoKQpgYGAKClRoZXJlIGFyZSBtYW55IHRoZW1lcyBhdmFpbGFibGUsIHlvdSBjYW4gc2VlIHNvbWUgaW4gdGhlIFtSIGdyYXBoIGdhbGxlcnldKGh0dHBzOi8vd3d3LnItZ3JhcGgtZ2FsbGVyeS5jb20vMTkyLWdncGxvdC10aGVtZXMvKS4KCldlIGNhbiBhbHNvIG1vZGlmeSBwYXJ0cyBvZiB0aGUgdGhlbWUgaW5kaXZpZHVhbGx5LiBXZSBjYW4gcmVtb3ZlIHRoZSBncmV5IGJhY2tncm91bmQgYW5kIGdyaWQgbGluZXMgd2l0aCB0aGUgY29kZSBiZWxvdy4KCmBgYHtyfQpwIDwtIHAgKyB0aGVtZShwYW5lbC5iYWNrZ3JvdW5kID0gZWxlbWVudF9ibGFuaygpLCAKICAgICAgICAgICAgICAgcGFuZWwuZ3JpZC5tYWpvciA9IGVsZW1lbnRfYmxhbmsoKSwgCiAgICAgICAgICAgICAgIHBhbmVsLmdyaWQubWlub3IgPSBlbGVtZW50X2JsYW5rKCkpCnAKYGBgCgpXZSBjYW4gYWRkIGF4aXMgbGluZXMuCgpgYGB7cn0KcCA8LSBwICsgdGhlbWUoYXhpcy5saW5lID0gZWxlbWVudF9saW5lKHNpemU9MC4yLCBjb2xvdXIgPSAiYmxhY2siKSkKcApgYGAKCldlIGNhbiByZW1vdmUgdGhlIGxlZ2VuZCBjb21wbGV0ZWx5LgoKYGBge3J9CnAgPC0gcCArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikKcApgYGAKCldlIGNhbiBjZW50cmUgdGhlIHRpdGxlLiAKCmBgYHtyfQpwIDwtIHAgKyB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKcApgYGAKCiMjIyMgRXhlcmNpc2UKCk1ha2UgYSB2b2xjYW5vIHBsb3QgZm9yIHRoZSBiYXNhbCBjZWxscyB1c2luZyB0aGUgZmlsZSAiaHR0cHM6Ly96ZW5vZG8ub3JnL3JlY29yZC8yNTk2MzgyL2ZpbGVzL2xpbW1hLXZvb21fYmFzYWxwcmVnbmFudC1iYXNhbGxhY3RhdGUiLiBDb2xvdXIgZ2VuZXMgd2l0aCBhZGouUC52YWx1ZSA8IDAuMDEgYW5kIGEgbG9nRkMgb2YgPjIgKGFuZCA8IC0yKS4gWW91IGNhbiBjaG9vc2UgdG8gdXNlIGFueSBjb2xvdXJzIGFuZCBtb2RpZnkgaXQgd2hhdGV2ZXIgd2F5IHlvdSBsaWtlLgoKCiMjIEtleSBQb2ludHMKLSBXZSBjYW4gbWFrZSBhIHZvbGNhbm8gcGxvdCB3aXRoIGBnZW9tX3BvaW50KClgIGJ5IHBsb3R0aW5nIHRoZSBsb2cgZm9sZCBjaGFuZ2UgKGxvZ0ZDKSB2cyB0aGUgbmVnYXRpdmUgbG9nMTAgUCB2YWx1ZQotIFdlIGNhbiBhZGQgY29sdW1ucyB3aXRoIGRwbHlyJ3MgbXV0YXRlCi0gVG8gaGlnaGxpZ2h0IG9yIGxhYmVsIGdlbmVzIHdlIGFkZCBhIGNvbHVtbiB0byBzcGVjaWZ5IHdoaWNoIGdlbmVzIHRvIGNvbG91ciBvciBsYWJlbAotIFdlIGNhbiB1c2UgaWZlbHNlKCkgdG8gdGVzdCBpZiB2YWx1ZXMgbWVldCBzcGVjaWZpZWQgY29uZGl0aW9ucwotIFdlIHVzZSBjKCkgdG8gY29tYmluZSBtdWx0aXBsZSB2YWx1ZXMKLSBXZSB1c2UgJWluJSB0byBjaGVjayBpZiBhIHZhbHVlIGlzIGluIGEgc2V0IG9mIHZhbHVlcwotIFdlIGNhbiB1c2UgZ2dyZXBlbCB0byByZXBlbCBvdmVybGFwcGluZyBsYWJlbHMKLSBXZSB1c2UgdGhlbWVzIHRvIG1vZGlmeSB0aGUgbm9uLWRhdGEgcGFydHMgb2YgYSBnZ3Bsb3QK